package org.restler.spring.data; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.reflect.TypeToken; import org.restler.client.RestlerException; import org.restler.spring.data.proxy.ResourceProxyMaker; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import javax.persistence.EmbeddedId; import javax.persistence.Id; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; class SpringDataRestMessageConverter implements GenericHttpMessageConverter<Object> { private final ResourceProxyMaker resourceProxyMaker = new ResourceProxyMaker(); private static final ObjectMapper objectMapper = new ObjectMapper(); static { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @Override public boolean canRead(Type type, Class<?> aClass, MediaType mediaType) { if (!(type instanceof ParameterizedType)) { return false; } Class<?> resultClass = TypeToken.of( ((ParameterizedType) type).getRawType() ).getRawType(); return isList(resultClass); } @Override public Object read(Type type, Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException { JsonNode rootNode = objectMapper.readTree(httpInputMessage.getBody()); Class<?> resultClass = TypeToken.of( ((ParameterizedType) type).getRawType() ).getRawType(); Class<?> elementClass; try { elementClass = Class.forName(((ParameterizedType) type).getActualTypeArguments()[0].getTypeName()); } catch (ClassNotFoundException e) { throw new RestlerException(e); } if (isList(resultClass)) { JsonNode embedded = rootNode.get("_embedded"); if(embedded == null) { return new ArrayList<>(); } Optional<ArrayNode> first = StreamSupport.stream(embedded.spliterator(), false). filter(e -> e instanceof ArrayNode). map(e -> (ArrayNode) e). findFirst(); return first.map(objects -> mapToProxies(objects, elementClass)). orElse(new ArrayList()); } throw new HttpMessageNotReadableException("Unexpected response format"); } @Override public boolean canRead(Class<?> aClass, MediaType mediaType) { return true; } @Override public boolean canWrite(Class<?> aClass, MediaType mediaType) { return false; } @Override public boolean canWrite(Type type, Class<?> aClass, MediaType mediaType) { return false; } @Override public List<MediaType> getSupportedMediaTypes() { List<MediaType> supportedMediaTypes = new ArrayList<>(); supportedMediaTypes.add(MediaType.APPLICATION_JSON); supportedMediaTypes.add(MediaType.parseMediaType("application/x-spring-data-verbose+json")); return supportedMediaTypes; } @Override public Object read(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException { JsonNode rootNode = objectMapper.readTree(httpInputMessage.getBody()); return makeObject(aClass, rootNode); } private Object makeObject(Class<?> aClass, JsonNode rootNode) { try { HashMap<String, String> hrefs = getObjectHrefs(rootNode); Object entity = objectMapper.treeToValue(rootNode, aClass); setId(entity, aClass, getId(rootNode)); return resourceProxyMaker.make(aClass, entity, hrefs); } catch(JsonProcessingException e) { throw new RestlerException("Can't parse json to object.", e); } } @Override public void write(Object o, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException { } @Override public void write(Object o, Type type, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException { } private List<Object> mapToProxies(JsonNode objects, Class<?> elementClass) { return StreamSupport.stream(objects.spliterator(), false). map(o -> makeObject(elementClass, o)). collect(Collectors.toList()); } private HashMap<String, String> getObjectHrefs(JsonNode objectNode) { HashMap<String, String> result = new LinkedHashMap<>(); JsonNode linksNode = objectNode.get("_links"); Iterator<String> names = linksNode.fieldNames(); linksNode.forEach(node -> result.put(names.next(), node.get("href").toString().replace("\"", ""))); return result; } private Object getId(JsonNode objectNode) { String links = "_links"; JsonNode linksNode = objectNode.get(links); String self = "self"; JsonNode selfLink = linksNode.get(self); String selfLinkString = selfLink.toString(); int leftOffset = selfLinkString.lastIndexOf("/") + 1; int rightOffset = selfLinkString.indexOf('"', leftOffset); return selfLinkString.substring(leftOffset, rightOffset); } private void setId(Object object, Class<?> aClass, Object id) { Field[] fields = aClass.getDeclaredFields(); Class fieldClass; for (Field field : fields) { if (field.getDeclaredAnnotation(Id.class) != null || field.getDeclaredAnnotation(EmbeddedId.class) != null) { fieldClass = field.getType(); field.setAccessible(true); try { Object wrappedId = fieldClass.getConstructor(String.class).newInstance(id); field.set(object, wrappedId); field.setAccessible(false); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException e) { throw new RestlerException("Id setting failed", e); } } } } private boolean isList(Class<?> someClass) { if (someClass == null) { return false; } if (someClass.equals(List.class)) { return true; } for (Class<?> intrf : someClass.getInterfaces()) { if (isList(intrf)) { return true; } } return isList(someClass.getSuperclass()); } }